home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / ProgramD2.iso / J A V A / Java Development Kit V1.2 / jdk12-win32(1).exe / data1.cab / demos / demo / applets / Animator / Animator.java < prev    next >
Encoding:
Java Source  |  1998-12-01  |  28.0 KB  |  868 lines

  1. /*
  2.  * @(#)Animator.java    1.10 97/02/05
  3.  *
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  *
  6.  * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
  7.  * modify and redistribute this software in source and binary code form,
  8.  * provided that i) this copyright notice and license appear on all copies of
  9.  * the software; and ii) Licensee does not utilize the software in a manner
  10.  * which is disparaging to Sun.
  11.  *
  12.  * This software is provided "AS IS," without a warranty of any kind. ALL
  13.  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
  14.  * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
  15.  * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
  16.  * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
  17.  * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
  18.  * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
  19.  * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
  20.  * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
  21.  * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
  22.  * POSSIBILITY OF SUCH DAMAGES.
  23.  *
  24.  * This software is not designed or intended for use in on-line control of
  25.  * aircraft, air traffic, aircraft navigation or aircraft communications; or in
  26.  * the design, construction, operation or maintenance of any nuclear
  27.  * facility. Licensee represents and warrants that it will not use or
  28.  * redistribute the Software for such purposes.
  29.  */
  30.  
  31. import java.awt.*;
  32. import java.awt.event.*;
  33. import java.applet.Applet;
  34. import java.applet.AudioClip;
  35. import java.util.Vector;
  36. import java.util.Hashtable;
  37. import java.util.Enumeration;
  38. import java.net.URL;
  39. import java.net.MalformedURLException;
  40. import java.util.List;
  41. import java.util.ArrayList;
  42. import java.util.Iterator;
  43.  
  44. /**
  45.  * An applet that plays a sequence of images, as a loop or a one-shot.
  46.  * Can have a soundtrack and/or sound effects tied to individual frames.
  47.  * See the <a href="http://java.sun.com/applets/applets/Animator/">Animator
  48.  * home page</a> for details and updates.
  49.  *
  50.  * @author Herb Jellinek
  51.  * @version 1.10, 02/05/97
  52.  */
  53. public class Animator extends Applet implements Runnable, MouseListener {
  54.     int appWidth = 0;                // Animator width
  55.     int appHeight = 0;               // Animator height
  56.     Thread engine = null;            // Thread animating the images
  57.     boolean userPause = false;       // True if thread currently paused by user
  58.     boolean loaded = false;          // Can we paint yet?
  59.     boolean error = false;           // Was there an initialization error?
  60.     Animation animation = null;      // Animation this animator contains
  61.     String hrefTarget = null;        // Frame target of reference URL if any
  62.     URL hrefURL = null;              // URL link for information if any
  63.  
  64.     static final String sourceLocation = 
  65.                           "http://java.sun.com/applets/applets/Animator/";
  66.     static final String userInstructions = "shift-click for errors, info";
  67.     static final int STARTUP_ID    = 0;
  68.     static final int BACKGROUND_ID = 1;
  69.     static final int ANIMATION_ID  = 2;
  70.  
  71.     /**
  72.      * Applet info.
  73.      */
  74.     public String getAppletInfo() {
  75.     return "Animator v1.10 (02/05/97), by Herb Jellinek";
  76.     }
  77.  
  78.     /**
  79.      * Parameter info.
  80.      */
  81.     public String[][] getParameterInfo() {
  82.     String[][] info = {
  83.         {"imagesource",     "URL",            "a directory"},
  84.         {"startup",     "URL",            "image displayed at start-up"},
  85.         {"backgroundcolor", "int", "background color (24-bit RGB number)"},
  86.         {"background",     "URL",           "image displayed as background"},
  87.         {"startimage",     "int",            "index of first image"},
  88.         {"endimage",     "int",            "index of last image"},
  89.         {"namepattern",     "URL",         "generates indexed names"},
  90.         {"images",          "URLs",        "list of image indices"},
  91.         {"href",        "URL",           "page to visit on mouse-click"},
  92.         {"target",        "name",           "frame to put that page in"},
  93.         {"pause",             "int",            "global pause, milliseconds"},
  94.         {"pauses",             "ints",     "individual pauses, milliseconds"},
  95.         {"repeat",             "boolean",     "repeat? true or false"},
  96.         {"positions",       "coordinates", "path images will follow"},
  97.         {"soundsource",    "URL",            "audio directory"},
  98.         {"soundtrack",    "URL",            "background music"},
  99.         {"sounds",        "URLs",           "list of audio samples"},
  100.     };
  101.     return info;
  102.     }
  103.  
  104.     /**
  105.      * Show a crude "About" box.  Displays credits, errors (if any), and
  106.      * parameter values and documentation.
  107.      */
  108.     void showDescription() {
  109.     DescriptionFrame description = new DescriptionFrame();        
  110.     description.tell("\t\t"+getAppletInfo()+"\n");
  111.     description.tell("Updates, documentation at "+sourceLocation+"\n\n");
  112.     description.tell("Document base: "+getDocumentBase()+"\n");
  113.     description.tell("Code base: "+getCodeBase()+"\n\n");
  114.     
  115.     Object errors[] = animation.tracker.getErrorsAny();
  116.     if (errors != null) {
  117.         description.tell("Applet image errors:\n");
  118.         for (int i = 0; i < errors.length; i++) {
  119.         if (errors[i] instanceof Image) {
  120.             AnimationFrame frame = (AnimationFrame) 
  121.                                               animation.frames.get(i);
  122.                     URL url = frame.imageLocation;
  123.             if (url != null) {
  124.             description.tell(" "+url+" not loaded\n");
  125.             }
  126.         }
  127.         }
  128.         description.tell("\n");
  129.     }
  130.     if (animation.frames == null || animation.frames.size() == 0)
  131.         description.tell("\n** No images loaded **\n\n");
  132.     description.tell("Applet parameters:\n");
  133.     description.tell(" width = "+getParameter("WIDTH")+"\n");
  134.     description.tell(" height = "+getParameter("HEIGHT")+"\n");
  135.     String params[][] = getParameterInfo();
  136.     for (int i = 0; i < params.length; i++) {
  137.         String name = params[i][0];
  138.         description.tell(" "+name+" = "+getParameter(name)+
  139.                  "\t ["+params[i][2]+"]\n");
  140.     }
  141.     description.show();
  142.     }
  143.  
  144.     /**
  145.      * Local version of getParameter for debugging purposes.
  146.      */
  147.     public String getParam(String key) {
  148.     String result = getParameter(key);
  149.     return result;
  150.     }
  151.  
  152.     /**
  153.      * Get parameters and parse them
  154.      */
  155.     public void handleParams() {
  156.         try {
  157.         String param = getParam("IMAGESOURCE");
  158.         animation.imageSource = (param == null) ? getDocumentBase() :
  159.                 new URL(getDocumentBase(), param + "/");
  160.     
  161.         String href = getParam("HREF");
  162.         if (href != null) {
  163.         try {
  164.             hrefURL = new URL(getDocumentBase(), href);
  165.         } catch (MalformedURLException e) {
  166.             showParseError(e);
  167.         }
  168.         }
  169.  
  170.         hrefTarget = getParam("TARGET");
  171.         if (hrefTarget == null)
  172.         hrefTarget = "_top";
  173.         param = getParam("PAUSE");
  174.             if (param != null)
  175.                 animation.setGlobalPause(Integer.parseInt(param));
  176.         param = getParam("REPEAT");
  177.         animation.repeat = (param == null) ? true :
  178.                 (param.equalsIgnoreCase("yes") ||
  179.                  param.equalsIgnoreCase("true"));
  180.         int startImage = 1;
  181.         int endImage = 1;
  182.         param = getParam("ENDIMAGE");
  183.         if (param != null) {
  184.         endImage = Integer.parseInt(param);
  185.         param = getParam("STARTIMAGE");
  186.         if (param != null) {
  187.             startImage = Integer.parseInt(param);
  188.         }
  189.                 param = getParam("NAMEPATTERN");
  190.                 animation.prepareImageRange(startImage, endImage, param);
  191.         } else {
  192.         param = getParam("STARTIMAGE");
  193.         if (param != null) {
  194.             startImage = Integer.parseInt(param);
  195.             param = getParam("NAMEPATTERN");
  196.             animation.prepareImageRange(startImage, endImage, param);
  197.         } else {
  198.             param = getParam("IMAGES");
  199.             if (param == null) {
  200.             showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
  201.                    "specified.");
  202.             error = true;
  203.             return;
  204.             } else {
  205.             animation.parseImages(param, getParam("NAMEPATTERN"));
  206.             }
  207.         }
  208.         }
  209.  
  210.         param = getParam("BACKGROUND");
  211.         if (param != null)
  212.         animation.backgroundImageURL = new URL(animation.imageSource,
  213.                                                        param);
  214.         param = getParam("BACKGROUNDCOLOR");
  215.         if (param != null)
  216.         animation.backgroundColor = decodeColor(param);
  217.         param = getParam("STARTUP");
  218.         if (param != null)
  219.         animation.startUpImageURL = new URL(animation.imageSource, 
  220.                                                     param);
  221.         param = getParam("SOUNDSOURCE");
  222.         animation.soundSource = (param == null) ? animation.imageSource :
  223.                 new URL(getDocumentBase(), param + "/");    
  224.         param = getParam("SOUNDS");
  225.         if (param != null)
  226.         animation.parseSounds(param);
  227.         param = getParam("PAUSES");
  228.         if (param != null)
  229.         animation.parseDurations(param);
  230.         param = getParam("POSITIONS");
  231.         if (param != null) 
  232.         animation.parsePositions(param);
  233.         param = getParam("SOUNDTRACK");
  234.         if (param != null)
  235.         animation.soundTrackURL = new URL(
  236.                                           animation.soundSource, param);
  237.     } catch (MalformedURLException e) {
  238.         showParseError(e);
  239.     } catch (ParseException e) {
  240.         showParseError(e);
  241.     }
  242.     }
  243.  
  244.     private Color decodeColor(String s) {
  245.     int val = 0;
  246.     try {
  247.         if (s.startsWith("0x")) {
  248.         val = Integer.parseInt(s.substring(2), 16);
  249.         } else if (s.startsWith("#")) {
  250.         val = Integer.parseInt(s.substring(1), 16);
  251.         } else if (s.startsWith("0") && s.length() > 1) {
  252.         val = Integer.parseInt(s.substring(1), 8);
  253.         } else {
  254.         val = Integer.parseInt(s, 10);
  255.         }
  256.         return new Color(val);
  257.     } catch (NumberFormatException e) {
  258.         return null;
  259.     }
  260.     }
  261.     
  262.     /**
  263.      * Initialize the applet.  Get parameters.
  264.      */
  265.     public void init() {
  266.         
  267.         //animation.tracker = new MediaTracker(this);
  268.     appWidth = getSize().width;
  269.     appHeight = getSize().height;
  270.         animation = new Animation(this);
  271.         handleParams();
  272.         animation.init();
  273.     addMouseListener(this);
  274.         Thread me = Thread.currentThread();
  275.         me.setPriority(Thread.MIN_PRIORITY);
  276.         userPause = false;
  277.     }
  278.  
  279.     public void destroy() {
  280.         removeMouseListener(this);
  281.     }
  282.  
  283.     void tellLoadingMsg(String file, String fileType) {
  284.     showStatus("Animator: loading "+fileType+" "+file);
  285.     }
  286.  
  287.     void tellLoadingMsg(URL url, String fileType) {
  288.     tellLoadingMsg(url.toExternalForm(), fileType);
  289.     }
  290.  
  291.     void clearLoadingMessage() {
  292.     showStatus("");
  293.     }
  294.     
  295.     void loadError(String fileName, String fileType) {
  296.     String errorMsg = "Animator: Couldn't load "+fileType+" "+
  297.         fileName;
  298.     showStatus(errorMsg);
  299.     System.err.println(errorMsg);
  300.     error = true;
  301.     repaint();
  302.     }
  303.  
  304.     void loadError(URL badURL, String fileType) {
  305.     loadError(badURL.toExternalForm(), fileType);
  306.     }
  307.  
  308.     void showParseError(Exception e) {
  309.     String errorMsg = "Animator: Parse error: "+e;
  310.     showStatus(errorMsg);
  311.     System.err.println(errorMsg);
  312.     error = true;
  313.     repaint();
  314.     }
  315.  
  316.     /**
  317.      * Run the animation. This method is called by class Thread.
  318.      * @see java.lang.Thread
  319.      */
  320.     public void run() {
  321.         Thread me = Thread.currentThread();
  322.         if (animation.frames == null)
  323.             return;
  324.         if ((appWidth <= 0) || (appHeight <= 0))
  325.             return;
  326.     try {
  327.             while (engine == me) {
  328.                 // Get current frame and paint it, play its sound
  329.                 AnimationFrame thisFrame = (AnimationFrame) 
  330.                                animation.frames.get(animation.currentFrame);
  331.                 repaint();
  332.                 if (thisFrame.sound != null)
  333.                     thisFrame.sound.play();
  334.  
  335.                 animation.currentFrame++;
  336.                  // Check if we are done
  337.                 if (animation.currentFrame >= animation.frames.size()) {
  338.                     if (animation.repeat)
  339.                         animation.currentFrame = 0;
  340.                     else return;
  341.                 }
  342.  
  343.                 // Pause for duration or longer if user paused
  344.                 try {        
  345.                     Thread.sleep(thisFrame.duration);
  346.                     synchronized(this) {
  347.                         while (userPause) {
  348.                             animation.stopPlaying();
  349.                             wait();
  350.                         }
  351.                     }
  352.                 }
  353.                 catch (InterruptedException e) {
  354.                 }
  355.             }
  356.         } finally {
  357.             synchronized(this) {
  358.             if (engine == me)
  359.                 animation.stopPlaying();
  360.             }
  361.         }
  362.     }
  363.  
  364.     /**
  365.      * No need to clear anything; just paint.
  366.      */
  367.     public void update(Graphics g) {
  368.     paint(g);
  369.     }
  370.  
  371.     /**
  372.      * Paint the current frame
  373.      */
  374.     public void paint(Graphics g) {
  375.         if (error || ! loaded) {
  376.             if (animation.startUpImage != null) {
  377.         if (animation.tracker.checkID(STARTUP_ID)) {
  378.             if (animation.backgroundColor != null) {
  379.             g.setColor(animation.backgroundColor);
  380.             g.fillRect(0, 0, appWidth, appHeight);
  381.             }
  382.             g.drawImage(animation.startUpImage, 0, 0, this);
  383.         }
  384.         } else {
  385.         if ((animation.backgroundImage != null) &&
  386.              (animation.tracker.checkID(BACKGROUND_ID)))
  387.             g.drawImage(animation.backgroundImage, 0, 0, this);
  388.         else 
  389.             g.clearRect(0, 0, appWidth, appHeight);
  390.         }
  391.         } else {
  392.             animation.paint(g);
  393.         }
  394.     }
  395.  
  396.     /**
  397.      * Start the applet by forking an animation thread.
  398.      */
  399.     public void start() {
  400.     engine = new Thread(this);
  401.     engine.start();
  402.     showStatus(getAppletInfo());
  403.     }
  404.  
  405.     /**
  406.      * Stop the insanity, um, applet.
  407.      */
  408.     public synchronized void stop() {
  409.     engine = null;
  410.         animation.stopPlaying();
  411.          if (userPause) {
  412.             userPause = false;
  413.             notify();
  414.         }
  415.     }
  416.  
  417.     /**
  418.      * Pause the thread when the user clicks the mouse in the applet.
  419.      * If the thread has stopped (as in a non-repeat performance),
  420.      * restart it.
  421.      */
  422.     public synchronized void mousePressed(MouseEvent event) {
  423.         event.consume();
  424.     if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
  425.         showDescription();
  426.         return;
  427.     } else if (hrefURL != null) {
  428.             //Let mouseClicked handle this.
  429.             return;
  430.         } else if (loaded) {
  431.         userPause = !userPause;
  432.             if (!userPause) {
  433.                 animation.startPlaying();
  434.                 notifyAll();
  435.             }
  436.     }
  437.     }
  438.  
  439.     public void mouseClicked(MouseEvent event) {
  440.     if ((hrefURL != null) &&
  441.             ((event.getModifiers() & InputEvent.SHIFT_MASK) == 0)) {
  442.         getAppletContext().showDocument(hrefURL, hrefTarget);
  443.     }
  444.     }
  445.  
  446.     public void mouseReleased(MouseEvent event) {
  447.     }
  448.  
  449.     public void mouseEntered(MouseEvent event) {
  450.     showStatus(getAppletInfo()+" -- "+userInstructions);
  451.     }
  452.  
  453.     public void mouseExited(MouseEvent event) {
  454.         showStatus("");
  455.     }    
  456. }
  457.  
  458. /**
  459.  * A class that represents an animation to be displayed by the applet
  460.  */
  461. class Animation extends Object {
  462.     static final int STARTUP_ID    = 0;
  463.     static final int BACKGROUND_ID = 1;
  464.     static final int ANIMATION_ID  = 2;
  465.     static final String imageLabel = "image";
  466.     static final String soundLabel = "sound";
  467.  
  468.     int globalPause = 1300;        // global pause in milleseconds
  469.     List frames = null;            // List holding frames of animation
  470.     int currentFrame;              // Index into images for current position
  471.     Image startUpImage = null;     // The startup image if any
  472.     Image backgroundImage = null;  // The background image if any
  473.     AudioClip soundTrack = null;   // The soundtrack for this animation
  474.     Color backgroundColor = null;  // Background color if any
  475.     URL backgroundImageURL = null; // URL of background image if any
  476.     URL startUpImageURL = null;    // URL of startup image if any 
  477.     URL soundTrackURL = null;      // URL of soundtrack
  478.     URL imageSource = null;        // Directory or URL for images
  479.     URL soundSource = null;        // Directory or URL for sounds
  480.     boolean repeat;                // Repeat the animation if true
  481.     Image offScrImage;             // Offscreen image
  482.     Graphics offScrGC;             // Offscreen graphics context
  483.     MediaTracker tracker;          // MediaTracker used to load images
  484.     Animator owner;                // Applet that contains this animation
  485.  
  486.     Animation(Animator container) {
  487.         super();
  488.         owner = container;
  489.     }
  490.  
  491.     void init() {
  492.         tracker = new MediaTracker(owner);
  493.     currentFrame = 0;
  494.         loadAnimationMedia();
  495.     startPlaying();
  496.     }
  497.  
  498.     void setGlobalPause(int pause) {
  499.         globalPause = pause;
  500.     }
  501.  
  502.     /**
  503.      * Loads the images and sounds involved with this animation
  504.      */
  505.     void loadAnimationMedia() {
  506.         URL badURL;
  507.         boolean error;
  508.         try {
  509.             if (startUpImageURL != null) {
  510.                 owner.tellLoadingMsg(startUpImageURL, imageLabel);
  511.                 startUpImage = fetchImageAndWait(startUpImageURL, STARTUP_ID);
  512.                 if (tracker.isErrorID(STARTUP_ID)) {
  513.                     owner.loadError(startUpImageURL, "start-up image");
  514.                 }
  515.                 owner.repaint();
  516.             }
  517.         
  518.         if (backgroundImageURL != null) {
  519.              owner.tellLoadingMsg(backgroundImageURL, imageLabel);
  520.             backgroundImage = fetchImageAndWait(backgroundImageURL, 
  521.                                                     BACKGROUND_ID);
  522.             if (tracker.isErrorID(BACKGROUND_ID))
  523.                     owner.loadError(backgroundImageURL, 
  524.                                         "background image");
  525.         owner.repaint();
  526.             }
  527.  
  528.         // Fetch the animation frame images
  529.             Iterator iterator = frames.iterator();
  530.             while(iterator.hasNext()) {
  531.                 AnimationFrame frame = (AnimationFrame) iterator.next();
  532.                 owner.tellLoadingMsg(frame.imageLocation, imageLabel);
  533.                 frame.image = owner.getImage(frame.imageLocation);
  534.                 tracker.addImage(frame.image, ANIMATION_ID);
  535.                 try {
  536.                     tracker.waitForID(ANIMATION_ID);
  537.                 } catch (InterruptedException e) {}
  538.         }
  539.  
  540.         if (soundTrackURL != null && soundTrack == null) {
  541.         owner.tellLoadingMsg(soundTrackURL, imageLabel);
  542.         soundTrack = owner.getAudioClip(soundTrackURL);
  543.         if (soundTrack == null) {
  544.             owner.loadError(soundTrackURL, "soundtrack");
  545.             return;
  546.         }
  547.         }
  548.  
  549.             // Load the sounds into their frames
  550.             iterator = frames.iterator();
  551.             while(iterator.hasNext()) {
  552.                 AnimationFrame frame = (AnimationFrame) iterator.next();
  553.                 if (frame.soundLocation != null) {
  554.                     owner.tellLoadingMsg(frame.soundLocation, soundLabel);
  555.                     try {
  556.                         frame.sound = owner.getAudioClip(frame.soundLocation);
  557.                     } catch (Exception ex) {
  558.                         owner.loadError(frame.soundLocation, soundLabel);
  559.                     }
  560.                 }
  561.         }
  562.  
  563.         owner.clearLoadingMessage();
  564.         offScrImage = owner.createImage(owner.appWidth, owner.appHeight);
  565.         offScrGC = offScrImage.getGraphics();
  566.         offScrGC.setColor(Color.lightGray);
  567.             owner.loaded = true;
  568.         error = false;
  569.     } catch (Exception e) {
  570.         error = true;
  571.         e.printStackTrace();
  572.     }
  573.     }
  574.  
  575.     /**
  576.      * Fetch an image and wait for it to come in.  Used to enforce a load
  577.      * order for background and startup images.
  578.      */
  579.     Image fetchImageAndWait(URL imageURL, int trackerClass) 
  580.                               throws InterruptedException {
  581.     Image image = owner.getImage(imageURL);
  582.     tracker.addImage(image, trackerClass);
  583.     tracker.waitForID(trackerClass);
  584.     return image;
  585.     }
  586.  
  587.     /**
  588.      * Stuff a range of image names into a List
  589.      * @return a List of image URLs.
  590.      */
  591.     void prepareImageRange(int startImage, int endImage, String pattern)
  592.     throws MalformedURLException {
  593.     frames = new ArrayList(Math.abs(endImage - startImage) + 1);
  594.     if (pattern == null)
  595.         pattern = "T%N.gif";
  596.     if (startImage > endImage) {
  597.         for (int i = startImage; i >= endImage; i--) {
  598.                 AnimationFrame frame = new AnimationFrame();
  599.         frames.add(frame);
  600.                 frame.duration = globalPause;
  601.                 frame.imageLocation = new URL(imageSource, 
  602.                                               doSubst(pattern, i+""));
  603.         }
  604.     } else {
  605.         for (int i = startImage; i <= endImage; i++) {
  606.                 AnimationFrame frame = new AnimationFrame();
  607.         frames.add(frame);
  608.                 frame.duration = globalPause;
  609.                 frame.imageLocation = new URL(imageSource,
  610.                                               doSubst(pattern, i+""));
  611.         }
  612.     }
  613.     }
  614.  
  615.     /**
  616.      * Parse the SOUNDS parameter.  It looks like
  617.      * train.au||hello.au||stop.au, etc., where each item refers to a
  618.      * source image.  Empty items mean that the corresponding image
  619.      * has no associated sound.
  620.      */
  621.     void parseSounds(String attr) throws MalformedURLException {
  622.     int frameIndex = 0;
  623.     int numFrames = frames.size();
  624.     for (int i = 0; (i < attr.length()) && (frameIndex < numFrames); ) {
  625.         int next = attr.indexOf('|', i);
  626.         if (next == -1) next = attr.length();
  627.         String sound = attr.substring(i, next);
  628.         if (sound.length() != 0) {
  629.                 AnimationFrame frame = (AnimationFrame) frames.get(frameIndex);
  630.         frame.soundLocation = new URL(soundSource, sound);
  631.         }
  632.         i = next + 1;
  633.         frameIndex++;
  634.         }
  635.     }
  636.  
  637.     /**
  638.      * Parse the IMAGES parameter.  It looks like
  639.      * 1|2|3|4|5, etc., where each number (item) names a source image.
  640.      */
  641.     void parseImages(String attr, String pattern) 
  642.                            throws MalformedURLException {
  643.         frames = new ArrayList();
  644.     if (pattern == null)
  645.         pattern = "T%N.gif";
  646.     for (int i = 0; i < attr.length(); ) {
  647.         int next = attr.indexOf('|', i);
  648.         if (next == -1) next = attr.length();
  649.         String file = attr.substring(i, next);
  650.             AnimationFrame frame = new AnimationFrame();
  651.             frames.add(frame);
  652.             frame.imageLocation = new URL(imageSource, doSubst(pattern, file));
  653.             frame.duration = globalPause;
  654.         i = next + 1;
  655.     }
  656.     }
  657.  
  658.     /**
  659.      * Parse the PAUSES parameter.  It looks like
  660.      * 1000|500|||750, etc., where each item corresponds to a
  661.      * source image.  Empty items mean that the corresponding image
  662.      * has no special duration, and should use the global one.
  663.      *
  664.      * @return a Hashtable of Integer pauses keyed to Integer
  665.      * frame numbers.
  666.      */
  667.     void parseDurations(String attr) {
  668.     int imageNum = 0;
  669.     int numImages = frames.size();
  670.     for (int i = 0; (i < attr.length()) && (imageNum < numImages); ) {
  671.         int next = attr.indexOf('|', i);
  672.         if (next == -1) next = attr.length();
  673.             AnimationFrame aFrame = (AnimationFrame) frames.get(imageNum);
  674.         if (i != next) {
  675.         int duration = Integer.parseInt(attr.substring(i, next));
  676.                 aFrame.duration = duration;
  677.         }
  678.         i = next + 1;
  679.         imageNum++;
  680.     }
  681.     }
  682.  
  683.     /**
  684.      * Parse a String of form xxx@yyy and return a Point.
  685.      */
  686.     Point parsePoint(String s) throws ParseException {
  687.     int atPos = s.indexOf('@');
  688.     if (atPos == -1) throw new ParseException("Illegal position: "+s);
  689.     return new Point(Integer.parseInt(s.substring(0, atPos)),
  690.              Integer.parseInt(s.substring(atPos + 1)));
  691.     }
  692.  
  693.     /**
  694.      * Parse the POSITIONS parameter.  It looks like
  695.      * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
  696.      * corresponding to a source image.  Empty items mean that the
  697.      * corresponding image has the same position as the preceding one.
  698.      *
  699.      * @return a Hashtable of Points keyed to Integer frame numbers.
  700.      */
  701.     void parsePositions(String param)
  702.     throws ParseException {
  703.     int imageNum = 0;
  704.     int numImages = frames.size();
  705.     for (int i = 0; (i < param.length()) && (imageNum < numImages); ) {
  706.         int next = param.indexOf('|', i);
  707.         if (next == -1) 
  708.                 next = param.length();
  709.         if (i != next) {
  710.                 AnimationFrame frame = (AnimationFrame) frames.get(imageNum);
  711.                 frame.position = parsePoint(param.substring(i, next));
  712.             }
  713.         i = next + 1;
  714.         imageNum++;
  715.     }
  716.     }
  717.  
  718.     /**
  719.      * Substitute an integer some number of times in a string, subject to
  720.      * parameter strings embedded in the string.
  721.      * Parameter strings:
  722.      *   %N - substitute the integer as is, with no padding.
  723.      *   %<digit>, for example %5 - substitute the integer left-padded with
  724.      *        zeros to <digits> digits wide.
  725.      *   %% - substitute a '%' here.
  726.      * @param inStr the String to substitute within
  727.      * @param theInt the int to substitute, as a String.
  728.      */
  729.     String doSubst(String inStr, String theInt) {
  730.     String padStr = "0000000000";
  731.     int length = inStr.length();
  732.     StringBuffer result = new StringBuffer(length);
  733.     
  734.     for (int i = 0; i < length;) {
  735.         char ch = inStr.charAt(i);
  736.         if (ch == '%') {
  737.         i++;
  738.         if (i == length) {
  739.             result.append(ch);
  740.         } else {
  741.             ch = inStr.charAt(i);
  742.             if (ch == 'N' || ch == 'n') {
  743.             // just stick in the number, unmolested
  744.             result.append(theInt);
  745.             i++;
  746.             } else {
  747.             int pad;
  748.             if ((pad = Character.digit(ch, 10)) != -1) {
  749.                 // we've got a width value
  750.                 String numStr = theInt;
  751.                 String scr = padStr+numStr;
  752.                 result.append(scr.substring(scr.length() - pad));
  753.                 i++;
  754.             } else {
  755.                 result.append(ch);
  756.                 i++;
  757.             }
  758.             }
  759.         }
  760.         } else {
  761.         result.append(ch);
  762.         i++;
  763.         }
  764.     }
  765.     return result.toString();
  766.     }    
  767.  
  768.     void startPlaying() {
  769.     if (soundTrack != null)
  770.         soundTrack.loop();
  771.     }
  772.  
  773.     void stopPlaying() {
  774.     if (soundTrack != null)
  775.         soundTrack.stop();
  776.     }
  777.  
  778.     public void paint(Graphics g) {
  779.         int xPos = 0;
  780.         int yPos = 0;
  781.         if ((frames.size() > 0) && tracker.checkID(ANIMATION_ID) && 
  782.                                                           (offScrGC != null)) {
  783.             AnimationFrame frame = (AnimationFrame) frames.get(currentFrame);
  784.             Image image = frame.image;
  785.             if (backgroundImage == null) {
  786.                 offScrGC.clearRect(0, 0, owner.appWidth, owner.appHeight);
  787.             }
  788.             else
  789.                 offScrGC.drawImage(backgroundImage, 0, 0, owner);
  790.             Point pos = null;
  791.             if (frame.position != null) {
  792.                 xPos = frame.position.x;
  793.                 yPos = frame.position.y;
  794.             }
  795.             if (backgroundColor != null) {
  796.                 offScrGC.setColor(backgroundColor);
  797.                 offScrGC.fillRect(0, 0, owner.appWidth, owner.appHeight);
  798.                 offScrGC.drawImage(image, xPos, yPos, backgroundColor, owner);
  799.             } else {
  800.                 offScrGC.drawImage(image, xPos, yPos, owner);
  801.             }
  802.             if (offScrImage != null)
  803.                 g.drawImage(offScrImage, 0, 0, owner);
  804.         }
  805.     }
  806. }
  807.  
  808. /**
  809.  * Instances of this class represent a single frame of an animation
  810.  * There can be an image, sound, and position associated with each frame
  811.  */
  812. class AnimationFrame extends Object {
  813.     static final String imageLabel = "image";
  814.     static final String soundLabel = "sound";
  815.  
  816.     URL imageLocation = null; // Directory or URL of this frames image
  817.     URL soundLocation = null; // Directory or URL of this frames sound
  818.     int duration;             // Duration time for this frame in milliseconds
  819.     AudioClip sound;          // Sound associated with this frame object
  820.     Image image;              // Image associated with this frame
  821.     Point position;           // Position of this frame
  822.  
  823. }
  824.  
  825. /**
  826.  * ParseException: signals a parameter parsing problem.
  827.  */
  828. class ParseException extends Exception {
  829.     ParseException(String s) {
  830.     super(s);
  831.     }
  832. }
  833.  
  834. /**
  835.  * DescriptionFrame: implements a pop-up "About" box.
  836.  */
  837. class DescriptionFrame extends Frame implements ActionListener {
  838.     static final int rows = 27;
  839.     static final int cols = 70;
  840.     TextArea info;
  841.     Button cancel;
  842.  
  843.     DescriptionFrame() {
  844.     super("Animator v1.10");
  845.     add("Center", info = new TextArea(rows, cols));
  846.     info.setEditable(false);
  847.     info.setBackground(Color.white);
  848.     Panel buttons = new Panel();
  849.     add("South", buttons);
  850.     buttons.add(cancel = new Button("Cancel"));
  851.     cancel.addActionListener(this);
  852.     pack();
  853.     }
  854.  
  855.     public void show() {
  856.     info.select(0,0);
  857.     super.show();
  858.     }
  859.  
  860.     void tell(String s) {
  861.     info.append(s);
  862.     }
  863.  
  864.     public void actionPerformed(ActionEvent e) {
  865.     setVisible(false);
  866.     }
  867. }
  868.